﻿using K3Cloud.WebApi.Core.IoC.Attributes;
using K3Cloud.WebApi.Core.IoC.DataEntity;
using K3Cloud.WebApi.Core.IoC.Extensions;
using K3Cloud.WebApi.Core.IoC.Types;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SQLBuilder.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using K3Ioc = K3Cloud.WebApi.Core.IoC.K3IOCServiceCollectionExtensions;

namespace K3Cloud.WebApi.Core.IoC.K3Client
{
    public sealed class K3Queryable<T> : IK3Queryable<T> where T : class
    {
        private Expression<Func<T, bool>> Filter { get; set; }
        private Expression<Func<T, object>> OrderExpression { get; set; }
        private OrderType[]? OrderTypes { get; set; }
        private QueryParam? QueryParam { get; set; }
        private readonly int topRowCount = 0;
        private int startRow = 0;
        private int limit = 0;
        /// <summary>
        /// K3Cloud表单查询链式请求
        /// </summary>
        public K3Queryable() : this(default, default)
        {
        }
        /// <summary>
        /// K3Cloud表单查询链式请求
        /// </summary>
        /// <param name="whereExpression">where筛选条件</param>
        /// <param name="queryParam"></param>
        public K3Queryable(Expression<Func<T, bool>>? whereExpression = default, QueryParam? queryParam = default)
        {
            if (whereExpression != null)
            {
                Filter = Filter.AndFilter(whereExpression);
            }
            QueryParam = queryParam;
        }

        private bool IsCache { get; set; }
        private string CacheKey { get; set; } = string.Empty;
        private int CacheDurationInSeconds { get; set; }
        public IK3Queryable<T> WithCache(string cacheKey, int cacheDurationInSeconds = int.MaxValue)
        {
            CacheKey = cacheKey;
            CacheDurationInSeconds = cacheDurationInSeconds;
            IsCache = true;
            return this;
        }
        public IK3Queryable<T> WithCache(int cacheDurationInSeconds = int.MaxValue)
        {
            CacheDurationInSeconds = cacheDurationInSeconds;
            IsCache = true;
            return this;
        }
        public List<T>? ToList()
        {
            return ToListAsync().GetAwaiter().GetResult();
        }
        public async Task<List<T>?> ToListAsync(CancellationToken cancellation = default)
        {
            if (QueryParam != null)
            {
                if (IsCache)
                {
                    string key = string.IsNullOrEmpty(CacheKey) ? "QUERY_" + QueryParam.GenerateCacheKey() : CacheKey;
                    if (K3Ioc.CacheService.ContainsKey<List<T>>(key)) { return K3Ioc.CacheService.Get<List<T>>(key); }
                    else
                    {
                        string? jsonNoCache = await K3Scoped.Client.NativeBillQueryAsync(QueryParam, cancellation);
                        IEnumerable<JObject> replacedResult = jsonReplace(jsonNoCache);
                        jsonNoCache = JsonConvert.SerializeObject(replacedResult);
                        IEnumerable<T>? Result = JsonConvert.DeserializeObject<IEnumerable<T>>(jsonNoCache);
                        K3Ioc.CacheService.Add(key, Result?.ToList(), CacheDurationInSeconds);
                        return Result?.ToList();
                    }
                }
                string? queryResult = await K3Scoped.Client.NativeBillQueryAsync(QueryParam, cancellation);
                string jsonResult = JsonConvert.SerializeObject(jsonReplace(queryResult));
                IEnumerable<T>? deserializedResult = JsonConvert.DeserializeObject<IEnumerable<T>>(jsonResult);
                return deserializedResult?.ToList();
            }
            if (IsCache)
            {
                string FieldKeys = string.Join(",", typeof(T).GetProperties().Where(t => t.GetCustomAttributes(typeof(ignoreAttribute), true).Length == 0).Select(t => t.Name).ToArray());
                StringBuilder cacheKeyBuilder = new();
                _ = cacheKeyBuilder.Append("QUERY_");
                _ = cacheKeyBuilder.Append("FormId=").Append(typeof(T).GetAlias());
                _ = cacheKeyBuilder.Append("|FieldKeys=").Append(FieldKeys);
                _ = cacheKeyBuilder.Append("|FilterString=").Append(ExpressionBuilder.GetFilterString(Filter));
                _ = cacheKeyBuilder.Append("|OrderString=").Append(ExpressionBuilder.GetOrderString(OrderExpression, OrderTypes));
                _ = cacheKeyBuilder.Append("|StartRow=").Append(startRow);
                _ = cacheKeyBuilder.Append("|Limit=").Append(limit);
                _ = cacheKeyBuilder.Append("|TopRowCount=").Append(topRowCount);
                string tmpKey = string.IsNullOrEmpty(CacheKey) ? cacheKeyBuilder.ToString() : CacheKey;
                if (K3Ioc.CacheService.ContainsKey<List<T>>(tmpKey)) { return K3Ioc.CacheService.Get<List<T>>(tmpKey); }
                else
                {
                    List<T> rst = await K3Scoped.Client.ExecuteBillQueryAsync(Filter, OrderExpression, OrderTypes, topRowCount, startRow, limit, cancellation);
                    K3Ioc.CacheService.Add(tmpKey, rst, CacheDurationInSeconds);
                    return rst;
                }
            }
            return await K3Scoped.Client.ExecuteBillQueryAsync(Filter, OrderExpression, OrderTypes, topRowCount, startRow, limit, cancellation);
            static IEnumerable<JObject> jsonReplace(string? json)
            {
                if (json == null) { return []; }
                var result = JsonConvert.DeserializeObject<IEnumerable<JObject>?>(json);
                if (result == null) { return []; }
                return result.Select(j =>
                {
                    JObject newJ = [];
                    foreach (JProperty prop in j.Properties())
                    {
                        newJ[prop.Name.Replace(".", "_")] = prop.Value;
                    }
                    return newJ;
                });
            }
        }
        public async Task<List<T>?> ToPageListAsync(int pageNumber, int pageSize, CancellationToken cancellation = default)
        {
            startRow = pageNumber <= 0 ? 0 : (pageNumber - 1) * pageSize;
            if (pageSize <= 0)
            {
                pageSize = 10;
            }
            limit = pageSize;
            if (QueryParam != null)
            {
                QueryParam.StartRow = startRow;
                QueryParam.Limit = limit;
            }
            return await ToListAsync(cancellation);
        }
        public async Task<List<T>?> ToPageListAsync(int pageNumber, int pageSize, Refasync<int> totalNumber, CancellationToken cancellation = default)
        {
            totalNumber.Value = await CountAsync(cancellation);
            return await ToPageListAsync(pageNumber, pageSize, cancellation);
        }
        public async Task<List<T>?> ToPageListAsync(int pageNumber, int pageSize, Expression<Func<T, object>> selector, Refasync<int> totalNumber, CancellationToken cancellation = default)
        {
            totalNumber.Value = await CountAsync(selector, cancellation);
            return await ToPageListAsync(pageNumber, pageSize, cancellation);
        }
        public async Task<List<T>?> ToPageListAsync(int pageNumber, int pageSize, string countField, Refasync<int> totalNumber, CancellationToken cancellation = default)
        {
            totalNumber.Value = await CountAsync(countField, cancellation);
            return await ToPageListAsync(pageNumber, pageSize, cancellation);
        }
        public async Task<List<T>?> ToPageListAsync(int pageNumber, int pageSize, Refasync<int> totalNumber, Refasync<int> totalPage, CancellationToken cancellation = default)
        {
            totalNumber.Value = await CountAsync(cancellation);
            totalPage.Value = (totalNumber.Value + pageSize - 1) / pageSize;
            return await ToPageListAsync(pageNumber, pageSize, cancellation);
        }
        public async Task<int> CountAsync(CancellationToken cancellation = default)
        {
            string? fieldName = typeof(T).GetProperties()
                .Where(t => t.GetCustomAttributes(typeof(ignoreAttribute), true).Length == 0 &&
                (t.PropertyType == typeof(string) ||
                t.PropertyType == typeof(int) ||
                t.PropertyType == typeof(int?) ||
                t.PropertyType == typeof(float) ||
                t.PropertyType == typeof(float?) ||
                t.PropertyType == typeof(double) ||
                t.PropertyType == typeof(double?) ||
                t.PropertyType == typeof(decimal) ||
                t.PropertyType == typeof(decimal?))
            ).Select(t => t.GetCustomAttribute<AliasAttribute>()?.Name ?? t.Name).LastOrDefault();
            if (string.IsNullOrEmpty(fieldName))
            {
                throw new ArgumentNullException(nameof(fieldName));
            }
            fieldName = fieldName.Replace("__", ".");
            return await CountAsync(fieldName, cancellation);
        }
        public async Task<int> CountAsync(Expression<Func<T, object>> selector, CancellationToken cancellation = default)
        {
            if (selector.Body is NewExpression newExpression)
            {
                string firstName = newExpression.Members?.Cast<PropertyInfo>().Select(t => t.GetCustomAttribute<AliasAttribute>()?.Name ?? t.Name).FirstOrDefault() ?? string.Empty;
                if (string.IsNullOrEmpty(firstName)) throw new ArgumentException("计数字段为何如此复杂,用法参考：t=>t.FMaterialName");
                firstName = firstName.Replace("__", ".");
                return await CountAsync(firstName, cancellation);
            }
            MemberExpression? memberSelectorExpression = selector.Body as MemberExpression ?? ((UnaryExpression)selector.Body).Operand as MemberExpression;
            if (memberSelectorExpression != null)
            {
                PropertyInfo property = (PropertyInfo)memberSelectorExpression.Member;
                if (property != null)
                {
                    string firstName = property.GetCustomAttribute<AliasAttribute>()?.Name ?? property.Name;
                    firstName = firstName.Replace("__", ".");
                    return await CountAsync(firstName, cancellation);
                }
            }
            throw new ArgumentException("计数字段为何如此复杂,用法参考：t=>t.FMaterialName");
        }
        public async Task<int> CountAsync(string countField, CancellationToken cancellation = default)
        {
            if (IsCache && !string.IsNullOrEmpty(countField))
            {
                string filterString = ExpressionBuilder.GetFilterString(Filter);
                string tmpKey = QueryParam != null
                    ? string.IsNullOrEmpty(CacheKey) ? "COUNT_" + QueryParam.GenerateCacheKey() + countField : CacheKey + countField
                    : string.IsNullOrEmpty(CacheKey) ? $"COUNT_{countField}_{filterString}_{typeof(T).GetAlias()}_{startRow}_{limit}" : CacheKey + countField;
                if (K3Ioc.CacheService.ContainsKey<int>(tmpKey)) { return K3Ioc.CacheService.Get<int>(tmpKey); }
                else
                {
                    if (QueryParam != null)
                    {
                        int qrst = await K3Scoped.Client.GetTotalCountAsync(QueryParam.FormId, QueryParam.FilterString, countField, cancellation);
                        K3Ioc.CacheService.Add(tmpKey, qrst, CacheDurationInSeconds);
                        return qrst;
                    }
                    int rst = await K3Scoped.Client.GetTotalCountAsync(Filter, countField, cancellation);
                    K3Ioc.CacheService.Add(tmpKey, rst, CacheDurationInSeconds);
                    return rst;
                }
            }
            return QueryParam != null
                ? await K3Scoped.Client.GetTotalCountAsync(QueryParam.FormId, QueryParam.FilterString, countField, cancellation)
                : await K3Scoped.Client.GetTotalCountAsync(Filter, countField, cancellation);
        }

        public async Task<Dictionary<string, decimal>?> SumAsync(CancellationToken cancellation = default, params string[] sumFiled)
        {
            if (IsCache && sumFiled.Length != 0)
            {
                string tmpKey = QueryParam != null
                    ? string.IsNullOrEmpty(CacheKey) ? $"SUM_{string.Join("_", sumFiled)}_{QueryParam.GenerateCacheKey()}" : CacheKey + string.Join("_", sumFiled)
                    : string.IsNullOrEmpty(CacheKey) ? $"SUM_{string.Join("_", sumFiled)}_{typeof(T).GetAlias()}_{ExpressionBuilder.GetFilterString(Filter)}_{startRow}_{limit}" : CacheKey + string.Join("_", sumFiled);
                if (K3Ioc.CacheService.ContainsKey<Dictionary<string, decimal>>(tmpKey)) { return K3Ioc.CacheService.Get<Dictionary<string, decimal>>(tmpKey); }
                else
                {
                    Dictionary<string, decimal>? rst = await K3Scoped.Client.GetSumAsync(Filter, cancellation, sumFiled);
                    K3Ioc.CacheService.Add(tmpKey, rst, CacheDurationInSeconds);
                    return rst;
                }
            }
            return await K3Scoped.Client.GetSumAsync(Filter, cancellation, sumFiled);
        }

        public async Task<Dictionary<string, decimal>?> SumAsync(params string[] sumFiled)
        {
            return await SumAsync(default, sumFiled);
        }
        public async Task<Dictionary<string, decimal>?> SumAsync(Expression<Func<T, object>> selector, CancellationToken cancellation = default)
        {
            if (selector.Body is NewExpression memberExpression)
            {
                string[]? arrayNames = memberExpression.Members?.Cast<PropertyInfo>()
                    .Where(t => t.PropertyType == typeof(int) ||
                        t.PropertyType == typeof(int?) ||
                        t.PropertyType == typeof(short) ||
                        t.PropertyType == typeof(short?) ||
                        t.PropertyType == typeof(long) ||
                        t.PropertyType == typeof(long?) ||
                        t.PropertyType == typeof(float) ||
                        t.PropertyType == typeof(float?) ||
                        t.PropertyType == typeof(double) ||
                        t.PropertyType == typeof(double?) ||
                        t.PropertyType == typeof(decimal) ||
                        t.PropertyType == typeof(decimal?) ||
                        t.PropertyType == typeof(byte) ||
                        t.PropertyType == typeof(byte?) ||
                        t.PropertyType == typeof(sbyte?) ||
                        t.PropertyType == typeof(sbyte?) ||
                        t.PropertyType == typeof(ushort?) ||
                        t.PropertyType == typeof(ushort?) ||
                        t.PropertyType == typeof(uint?) ||
                        t.PropertyType == typeof(uint?) ||
                        t.PropertyType == typeof(ulong?) ||
                        t.PropertyType == typeof(ulong?)
                ).Select(t => t.Name).ToArray();
                return arrayNames?.Length == 0 ? throw new ArgumentException("不能对非数字字段进行sum运算") : await SumAsync(cancellation, arrayNames!);
            }
            else
            {
                MemberExpression memberSelectorExpression = (selector.Body as MemberExpression ?? ((UnaryExpression)selector.Body).Operand as MemberExpression) ?? throw new ArgumentException("参数错误:请参考 t=>t.FQty 或 t=> new {t.FQty,t.FBaseQty}");
                PropertyInfo property = (PropertyInfo)memberSelectorExpression.Member;
                return property != null
                    ? property.PropertyType == typeof(int) ||
                        property.PropertyType == typeof(int?) ||
                        property.PropertyType == typeof(short) ||
                        property.PropertyType == typeof(short?) ||
                        property.PropertyType == typeof(long) ||
                        property.PropertyType == typeof(long?) ||
                        property.PropertyType == typeof(float) ||
                        property.PropertyType == typeof(float?) ||
                        property.PropertyType == typeof(double) ||
                        property.PropertyType == typeof(double?) ||
                        property.PropertyType == typeof(decimal) ||
                        property.PropertyType == typeof(decimal?) ||
                        property.PropertyType == typeof(byte) ||
                        property.PropertyType == typeof(byte?) ||
                        property.PropertyType == typeof(sbyte?) ||
                        property.PropertyType == typeof(sbyte?) ||
                        property.PropertyType == typeof(ushort?) ||
                        property.PropertyType == typeof(ushort?) ||
                        property.PropertyType == typeof(uint?) ||
                        property.PropertyType == typeof(uint?) ||
                        property.PropertyType == typeof(ulong?) ||
                        property.PropertyType == typeof(ulong?)
                        ? await SumAsync(cancellation, property.Name)
                        : throw new ArgumentException("不能对非数字字段进行sum运算")
                    : default;
            }
        }
        public IK3Queryable<T> Where(Expression<Func<T, bool>> expression)
        {
            if (QueryParam != null) { throw new K3ApiException("查询链遵循顺序where->order->select->result"); }
            if (expression == null)
            {
                return this;
            }

            Filter = Filter.AndFilter(expression);
            return this;
        }
        public IK3Queryable<T> WhereIF(bool isWhere, Expression<Func<T, bool>> expression)
        {
            return isWhere ? Where(expression) : this;
        }
        public IK3Queryable<T> OrderBy(Expression<Func<T, object>> expression, OrderType[]? orders = default)
        {
            if (QueryParam != null) { throw new K3ApiException("查询链遵循顺序where->order->select->result"); }
            OrderExpression = expression;
            OrderTypes = orders;
            return this;
        }
        public IK3Queryable<T> Skip(int count)
        {
            startRow = count;
            if (QueryParam != null) QueryParam.StartRow = count;
            return this;
        }
        public IK3Queryable<T> Take(int count)
        {
            limit = count;
            if (QueryParam != null) QueryParam.Limit = count;
            return this;
        }
        public IK3Queryable<T> Clone()
        {
            return (IK3Queryable<T>)MemberwiseClone();
        }
        public async Task<T?> FirstAsync(CancellationToken cancellation = default)
        {
            limit = 1;
            List<T>? queryList = await ToListAsync(cancellation);
            return queryList?.FirstOrDefault();
        }
        public IK3Queryable<TResult> Select<TResult>(Expression<Func<T, TResult>> selector) where TResult : class
        {
            List<string>? sArr = [];
            if (selector.Body is NewExpression memberExpression)
            {
                sArr = memberExpression.Members?.Cast<PropertyInfo>().Select(t => GetFieldNames(t.Name)).ToList();
            }
            else
            {
                // 如用t=>t.FQty，那么TResult类型为double；服务端json为[{FQty:123.00},{FQty:456.00}]反序列后不能识别
                // 所以统一使用t=>new {t.FQty}
                throw new ArgumentException("参数错误:请参考 t=> new {t.FQty,t.FBaseQty}");
            }
            sArr ??= [];
            QueryParam query = new()
            {
                FormId = typeof(T).GetAlias(),
                FieldKeys = string.Join(",", sArr),
                FilterString = ExpressionBuilder.GetFilterString(Filter),
                OrderString = ExpressionBuilder.GetOrderString(OrderExpression, OrderTypes),
                TopRowCount = topRowCount,
                StartRow = startRow,
                Limit = limit
            };
            K3Queryable<TResult> nResult = new(null, query)
            {
                IsCache = IsCache,
                CacheKey = CacheKey,
                CacheDurationInSeconds = CacheDurationInSeconds
            };
            return nResult;
            static string GetFieldNames(string PropertyName)
            {
                return PropertyName.Contains("__")
                    ? PropertyName.Replace("__", ".") + " AS " + PropertyName
                    : typeof(T).GetProperties().Where(t => t.Name == PropertyName).Select(t => t.GetCustomAttribute<AliasAttribute>() != null ? t.GetCustomAttribute<AliasAttribute>()?.Name + " AS " + t.Name : t.Name).First();
            }
        }
        public IK3Queryable<T> Count(Expression<Func<T, object>> selector)
        {
            string filterString = string.Empty;
            if (QueryParam != null)
            {
                filterString = QueryParam.FilterString;
                QueryParam.FieldKeys = "count(1) AS BillCount";
                QueryParam.OrderString = string.Empty;
                QueryParam.StartRow = 0;
                QueryParam.Limit = 0;
                QueryParam.TopRowCount = 0;
            }
            else
            {
                QueryParam = new QueryParam
                {
                    FormId = typeof(T).GetAlias(),
                    FieldKeys = "count(1) AS BillCount",
                    FilterString = ExpressionBuilder.GetFilterString(Filter),
                    OrderString = string.Empty,
                    StartRow = 0,
                    Limit = 0,
                    TopRowCount = 0
                };
                filterString = QueryParam.FilterString;
            }
            if (selector.Body is NewExpression newExpression)
            {
                string firstName = newExpression.Members?.Cast<PropertyInfo>().Select(t => t.GetCustomAttribute<AliasAttribute>()?.Name ?? t.Name).FirstOrDefault() ?? string.Empty;
                if (string.IsNullOrEmpty(firstName)) { throw new ArgumentException("计数字段为何如此复杂,用法参考：t=>t.FMaterialName"); }
                firstName = firstName.Replace("__", ".");
                string filter = firstName + " IS NOT NULL";
                if (!string.IsNullOrEmpty(filterString))
                {
                    filter = filterString + " AND " + filter;
                }
                QueryParam.FilterString = filter;
            }
            MemberExpression? memberSelectorExpression = selector.Body as MemberExpression ?? ((UnaryExpression)selector.Body).Operand as MemberExpression;
            if (memberSelectorExpression != null)
            {
                PropertyInfo property = (PropertyInfo)memberSelectorExpression.Member;
                if (property != null)
                {
                    string firstName = property.GetCustomAttribute<AliasAttribute>()?.Name ?? property.Name;
                    firstName = firstName.Replace("__", ".");
                    string filter = firstName + " IS NOT NULL";
                    if (!string.IsNullOrEmpty(filterString))
                    {
                        filter = filterString + " AND " + filter;
                    }
                    QueryParam.FilterString = filter;
                }
            }
            return this;
        }
        public IK3Queryable<T> Sum(Expression<Func<T, object>> selector)
        {
            List<string>? sumFiled = [];
            if (selector.Body is NewExpression newExpression)
            {
                sumFiled = newExpression.Members?.Cast<PropertyInfo>().Select(t => t.GetCustomAttribute<AliasAttribute>()?.Name ?? t.Name).ToList();
            }
            else
            {
                MemberExpression? memberSelectorExpression = selector.Body as MemberExpression ?? ((UnaryExpression)selector.Body).Operand as MemberExpression;
                if (memberSelectorExpression != null)
                {
                    PropertyInfo property = (PropertyInfo)memberSelectorExpression.Member;
                    if (property != null)
                    {
                        string name = property.GetCustomAttribute<AliasAttribute>()?.Name ?? property.Name;
                        sumFiled.Add(name);
                    }
                }
            }
            List<string> fildStr = [];
            Expression<Func<T, bool>>? tmp = default;
            foreach (string item in sumFiled ?? [])
            {
                string item1 = item.Replace("__", ".");
                tmp = tmp.AddFilter(item, "<>", 0, EnumAndOr.Or);
                fildStr.Add($"sum({item1}) AS {item}");
            }
            string msql = ExpressionBuilder.GetFilterString(tmp);
            if (QueryParam != null)
            {
                QueryParam.FieldKeys = string.Join(",", fildStr);
                QueryParam.FilterString = string.IsNullOrEmpty(QueryParam.FilterString) ? msql : QueryParam.FilterString + $" AND ({msql})";
                QueryParam.OrderString = string.Empty;
                QueryParam.StartRow = 0;
                QueryParam.Limit = 0;
                QueryParam.TopRowCount = 0;
            }
            else
            {
                string filterString = ExpressionBuilder.GetFilterString(Filter);
                QueryParam = new QueryParam
                {

                    FormId = typeof(T).GetAlias(),
                    FieldKeys = string.Join(",", fildStr),
                    FilterString = string.IsNullOrEmpty(filterString) ? msql : filterString + $" AND ({msql})",
                    OrderString = string.Empty,
                    StartRow = 0,
                    Limit = 0,
                    TopRowCount = 0
                };

            }
            return this;
        }
        public string ToJson()
        {
            JsonSerializerSettings settings = new()
            {
                Formatting = Formatting.Indented
            };
            return QueryParam != null
                ? K3Scoped.Client.ToJson(QueryParam, settings)
                : K3Scoped.Client.ToJson(Filter, OrderExpression, OrderTypes, topRowCount, startRow, limit, settings);
        }
    }
}